package com.treasure.mall.biz.service.core.order.impl;

import cn.hutool.core.util.ArrayUtil;
import com.treasure.mall.biz.base.*;
import com.treasure.mall.biz.dto.BaseSaveDTO;
import com.treasure.mall.biz.dto.BaseSearchDTO;
import com.treasure.mall.biz.dto.condition.OrderSearchCondition;
import com.treasure.mall.biz.dto.order.DeliverDTO;
import com.treasure.mall.biz.entity.embeddable.GoodsInfo_;
import com.treasure.mall.biz.entity.embeddable.MerchantData_;
import com.treasure.mall.biz.entity.embeddable.ReceivingInfo_;
import com.treasure.mall.biz.entity.order.*;
import com.treasure.mall.biz.entity.user.MerchantPO_;
import com.treasure.mall.biz.entity.user.UserPO;
import com.treasure.mall.biz.entity.user.UserPO_;
import com.treasure.mall.biz.remote.track.LogisticsProvider;
import com.treasure.mall.biz.remote.wx.WxTemplateMessageService;
import com.treasure.mall.biz.remote.wx.dto.DeliverTemplateDTO;
import com.treasure.mall.biz.repository.order.DeliverRepository;
import com.treasure.mall.biz.repository.order.SubOrderRepository;
import com.treasure.mall.biz.service.core.order.RebateBillService;
import com.treasure.mall.biz.service.core.order.SettlementBillService;
import com.treasure.mall.biz.service.core.order.SubOrderLogService;
import com.treasure.mall.biz.service.core.order.SubOrderService;
import com.treasure.mall.biz.service.core.product.ProductService;
import com.treasure.mall.zbq.common.ExcelHelper;
import com.treasure.mall.zbq.common.Jsr310Utils;
import com.treasure.mall.zbq.common.RandomUtils;
import com.treasure.mall.zbq.common.base.ApiResult;
import com.treasure.mall.zbq.common.base.ResultCodeEnum;
import com.treasure.mall.zbq.common.constant.Constant;
import com.treasure.mall.zbq.common.exception.ApiException;
import com.treasure.mall.zbq.common.ienum.base.CodeDescEnumHelper;
import com.treasure.mall.zbq.common.ienum.order.OrderEnums;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * @author allan
 * @version 1.0.0
 * @date 2020/6/11
 */
@Service
@Slf4j
public class SubOrderServiceImpl extends AbstractSearchableCrudService<SubOrderPO, String> implements SubOrderService {
    @Autowired
    private DeliverRepository deliverRepository;
    @Autowired
    private SubOrderLogService orderLogService;
    @Autowired(required = false)
    private LogisticsProvider logisticsProvider;
    @Autowired
    private ProductService productService;
    /**
     * 转正时间，单位天
     */
    @Value("${zbq.order.settle-timeout}")
    private long settleTimeout;
    @Autowired
    private Environment environment;

    private SubOrderRepository subOrderRepository;
    @Autowired
    private RebateBillService rebateBillService;
    @Autowired
    private SettlementBillService settlementBillService;
    @Resource
    private WxTemplateMessageService messageService;

    @Autowired
    protected SubOrderServiceImpl(SubOrderRepository repository) {
        super(repository);
        this.subOrderRepository = repository;
    }

    @Override
    protected SpecificationAndSort<SubOrderPO> specificationAndSort(BaseSearchDTO searchDTO) {
        OrderSearchCondition condition = (OrderSearchCondition) searchDTO;
        UserDetails userDetails = AuthHelper.getPrincipal();
        if (AuthHelper.isMerchant(userDetails)) {
            condition.setMerchantId(userDetails.getId());
        }

        Specification<SubOrderPO> specification = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            query.distinct(true);
            Path<DealOrderPO> orderPath = root.get(SubOrderPO_.orderPO);
            Path<UserPO> userPath = orderPath.get(OrderPO_.userPO);
            Join<SubOrderPO, OrderItemPO> join = root.join(SubOrderPO_.orderItemPOS);

            if (!StringUtils.isEmpty(condition.getGoodsKey())) {
                predicates.add(cb.or(
                        cb.like(join.get(OrderItemPO_.goodsInfo).get(GoodsInfo_.goodsName), likeAll(condition.getGoodsKey())),
                        cb.like(join.get(OrderItemPO_.goodsInfo).get(GoodsInfo_.goodsBn), likeAll(condition.getGoodsKey()))
                ));
            }
            if (!StringUtils.isEmpty(condition.getReceiver())) {
                predicates.add(cb.or(
                        cb.like(orderPath.get(DealOrderPO_.receivingInfo).get(ReceivingInfo_.userName), likeAll(condition.getReceiver())),
                        cb.like(orderPath.get(DealOrderPO_.receivingInfo).get(ReceivingInfo_.telNumber), likeAll(condition.getReceiver()))
                ));
            }
            if (!StringUtils.isEmpty(condition.getOrderId())) {
                predicates.add(cb.like(root.get(SubOrderPO_.subOrderId), likeLeft(condition.getOrderId())));
            }
            if (condition.getUserId() > 0) {
                predicates.add(cb.equal(userPath.get(UserPO_.userId), condition.getUserId()));
            }
            if (condition.getPayStatus() != -1) {
                OrderEnums.PayStatus payStatus = CodeDescEnumHelper.getEnumTypeByCode(OrderEnums.PayStatus.class, condition.getPayStatus());
                predicates.add(cb.equal(orderPath.get(OrderPO_.payStatus), payStatus));
            }
            if (condition.getStatus() != -1) {
                OrderEnums.Status status = CodeDescEnumHelper.getEnumTypeByCode(OrderEnums.Status.class, condition.getStatus());
                predicates.add(cb.equal(root.get(SubOrderPO_.status), status));
            }
            if (condition.getMerchantId() > 0) {
                predicates.add(cb.equal(root.get(SubOrderPO_.merchantPO).get(MerchantPO_.merchantId), condition.getMerchantId()));
            }
            if (condition.getDeliverStatus() != -1) {
                predicates.add(cb.equal(orderPath.get(OrderPO_.payStatus), OrderEnums.PayStatus.PAYED));
                predicates.add(cb.equal(join.get(OrderItemPO_.deliverStatus), condition.getDeliverStatus()));
            }
            if (!StringUtils.isEmpty(condition.getBeginTime())) {
                LocalDateTime beginTime = Jsr310Utils.DateTime.of(condition.getBeginTime());
                predicates.add(cb.greaterThanOrEqualTo(root.get(SubOrderPO_.createTime), beginTime));
            }
            if (!StringUtils.isEmpty(condition.getEndTime())) {
                LocalDateTime endTime = Jsr310Utils.DateTime.of(condition.getEndTime());
                predicates.add(cb.lessThanOrEqualTo(root.get(SubOrderPO_.createTime), endTime));
            }
            if (!StringUtils.isEmpty(condition.getPayBeginTime())) {
                LocalDateTime beginTime = Jsr310Utils.DateTime.of(condition.getBeginTime());
                predicates.add(cb.greaterThanOrEqualTo(orderPath.get(OrderPO_.payTime), beginTime));
            }
            if (!StringUtils.isEmpty(condition.getPayEndTime())) {
                LocalDateTime endTime = Jsr310Utils.DateTime.of(condition.getEndTime());
                predicates.add(cb.lessThanOrEqualTo(orderPath.get(OrderPO_.payTime), endTime));
            }
            if (!StringUtils.isEmpty(condition.getUserKey())) {
                predicates.add(cb.or(
                        cb.like(userPath.get(UserPO_.nickName), likeAll(condition.getUserKey())),
                        cb.like(userPath.get(UserPO_.mobile), likeAll(condition.getUserKey()))
                ));
            }

            /**
             * 售后状态
             */
            if (condition.getAfterSaleStatus() != -1) {
                predicates.add(cb.equal(join.get(OrderItemPO_.afterSaleStatus), condition.getAfterSaleStatus()));
            }


            /**
             *  根据上级用户ID，获取订单
             */
            if (condition.getParentId() != null) {
                predicates.add(cb.equal(userPath.get(UserPO_.parentId), condition.getParentId()));
            }


            /**
             * 店铺名称
             */
            if (!StringUtils.isEmpty(condition.getShopTitle())) {
                predicates.add(cb.like(root.get(SubOrderPO_.merchantPO).get(MerchantPO_.data).get(MerchantData_.title), likeAll(condition.getShopTitle())));
            }


            //前端筛选
            if (condition.getUserOrderStatus() != -1) {
                OrderEnums.UserOrderStatus userOrderStatus = CodeDescEnumHelper.getEnumTypeByCode(OrderEnums.UserOrderStatus.class, condition.getUserOrderStatus());

                switch (userOrderStatus) {
                    case TO_PAY:
                        //待支付，支付失败的
                        predicates.add(cb.or(
                                cb.equal(orderPath.get(OrderPO_.payStatus), OrderEnums.PayStatus.TO_PAY),
                                cb.equal(orderPath.get(OrderPO_.payStatus), OrderEnums.PayStatus.FAILED)
                        ));
                        //并且活动中的
                        predicates.add(cb.equal(root.get(SubOrderPO_.status), OrderEnums.Status.ACTIVE));
                        break;
                    case TO_DELIVER:
                        predicates.add(cb.equal(orderPath.get(OrderPO_.payStatus), OrderEnums.PayStatus.PAYED));
                        predicates.add(cb.equal(root.get(SubOrderPO_.status), OrderEnums.Status.ACTIVE));
                        predicates.add(cb.equal(join.get(OrderItemPO_.deliverStatus), OrderEnums.DeliverStatus.TO_DELIVER));
                        break;
                    case TO_RECEIVE:
                        predicates.add(cb.equal(orderPath.get(OrderPO_.payStatus), OrderEnums.PayStatus.PAYED));
                        predicates.add(cb.equal(root.get(SubOrderPO_.status), OrderEnums.Status.ACTIVE));
                        predicates.add(cb.equal(join.get(OrderItemPO_.deliverStatus), OrderEnums.DeliverStatus.USER_TO_RECEIVE));
                        break;
                    case AFTER_SALE_ING:
                        predicates.add(cb.isNotNull(join.get(OrderItemPO_.afterSaleStatus)));
                        break;
                    case RECEIVED:
                        predicates.add(cb.equal(orderPath.get(OrderPO_.payStatus), OrderEnums.PayStatus.PAYED));
//                        predicates.add(cb.equal(join.get(OrderItemPO_.deliverStatus), OrderEnums.DeliverStatus.RECEIVED));
                        predicates.add(cb.or(
                                cb.equal(join.get(OrderItemPO_.deliverStatus), OrderEnums.DeliverStatus.RECEIVED),
                                cb.equal(root.get(SubOrderPO_.status), OrderEnums.Status.DONE)
                        ));
                        break;
                    default:
                        predicates.add(cb.equal(join.get(OrderItemPO_.deliverStatus), OrderEnums.DeliverStatus.USER_TO_RECEIVE));
                        break;

                }
            }
            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
        };

        Sort sort = new Sort(Sort.Direction.DESC, "createTime");

        return new SpecificationAndSort<>(specification, sort);
    }

    @Override
    public SubOrderPO save(BaseSaveDTO saveDTO) {
        return null;
    }

    @Override
    @Transactional
    public void deliver(DeliverDTO deliverDTO, UserDetails userDetails) {
        if (StringUtils.isEmpty(deliverDTO.getExpressNo())) {
            throw new ApiException(ApiResult.error("物流单号必填"));
        }
        OrderEnums.DeliverType deliverType;
        OrderEnums.DeliverRole from;

        switch (userDetails.getRoleType()) {
            case ORG_CENTER:
                deliverType = OrderEnums.DeliverType.IDENTIFY_ORG;
                from = OrderEnums.DeliverRole.ORG_CENTER;
                break;
            case ADMIN:
                deliverType = OrderEnums.DeliverType.PLATFORM;
                from = OrderEnums.DeliverRole.PLATFORM;
                break;
            default:
                deliverType = OrderEnums.DeliverType.MERCHANT;
                from = OrderEnums.DeliverRole.MERCHANT;
        }

        SubOrderPO subOrderPO = findById(deliverDTO.getOrderId());
        DeliverBillPO deliverBillPO = new DeliverBillPO();
        deliverBillPO.setId(RandomUtils.randomFlowNo());
        deliverBillPO.setSubOrderId(subOrderPO.getSubOrderId());
        deliverBillPO.setExpressNo(deliverDTO.getExpressNo());
        deliverBillPO.setExpressCom(deliverDTO.getExpressCompany());
        deliverBillPO.setExpressComCode(deliverDTO.getExpressComCode());
        deliverBillPO.setReceivingInfo(subOrderPO.getOrderPO().getReceivingInfo());
        deliverBillPO.setFrom(from);
        deliverBillPO.setMerchantPO(subOrderPO.getMerchantPO());
        deliverBillPO.setDeliverType(deliverType);
        deliverBillPO.setFreightAmount(deliverDTO.getFreightAmount());
        OrderEnums.DeliverStatus deliverStatus;
        if (subOrderPO.getOrderPO().isNeedIdentify() && subOrderPO.getDeliverStatus() == OrderEnums.DeliverStatus.TO_DELIVER) {
            //往鉴定中心走
            deliverStatus = OrderEnums.DeliverStatus.ORG_TO_RECEIVE;

            deliverBillPO.setTo(OrderEnums.DeliverRole.ORG_CENTER);
            // TODO: 2020/6/11 鉴定服务先不做了
        } else {
            deliverStatus = OrderEnums.DeliverStatus.USER_TO_RECEIVE;
            deliverBillPO.setTo(OrderEnums.DeliverRole.USER);
            //设置自动签收时间，14天
            if (environment.acceptsProfiles(Profiles.of(Constant.PROFILE_DEV, Constant.PROFILE_PRE, Constant.PROFILE_TEST))) {
                subOrderPO.setLastReceivedTime(LocalDateTime.now().plusMinutes(2));
            } else {
                subOrderPO.setLastReceivedTime(LocalDateTime.now().plusDays(7));
            }
        }
        deliverBillPO.setLastReceivedTime(subOrderPO.getLastReceivedTime());

        //现在就是一起发货
        List<DeliverItemPO> deliverItemPOS = new ArrayList<>();
        for (OrderItemPO orderItemPO : subOrderPO.getOrderItemPOS()) {
            DeliverItemPO deliverItemPO = new DeliverItemPO();
            deliverItemPO.setDeliverBillPO(deliverBillPO);
            deliverItemPO.setGoodsInfo(orderItemPO.getGoodsInfo());
            deliverItemPO.setNum(orderItemPO.getNum());
            deliverItemPO.setOrderItemId(orderItemPO.getId());
            orderItemPO.setDeliverStatus(deliverStatus);
            deliverItemPOS.add(deliverItemPO);

            //扣减真实库存，扣减锁定库存
            int updateStore = orderItemPO.getNum() * -1;
            productService.updateStores(orderItemPO.getGoodsInfo().getGoodsId(), updateStore, updateStore);
        }
        deliverBillPO.setDeliverItemPOS(deliverItemPOS);

        deliverRepository.save(deliverBillPO);

        repository.save(subOrderPO);

        //订单日志
        if (subOrderPO.getOrderPO().isNeedIdentify()) {
            orderLogService.log(subOrderPO.getSubOrderId(), OrderEnums.Operation.DELIVER, userDetails.getId(), userDetails.getUsername());
        }

        //订阅快递100服务
        if (deliverBillPO.getTo() == OrderEnums.DeliverRole.USER) {
            if (logisticsProvider == null) {
                log.info("快递服务订阅失败--没有可用的快递服务");
            } else {
                ApiResult apiResult = logisticsProvider.subscribe(deliverBillPO.getExpressComCode(), deliverBillPO.getExpressNo());
                if (!apiResult.isSuccess()) {
                    log.info("快递服务订阅失败--" + apiResult.getResultMsg());
                }
            }

            messageService.deliverSuccessMessage(
                    DeliverTemplateDTO
                            .builder()
                            .userId(subOrderPO.getOrderPO().getUserPO().getUserId())
                            .expressNo(deliverBillPO.getExpressNo())
                            .expressCom(deliverBillPO.getExpressCom())
                            .receivingInfo(deliverBillPO.getReceivingInfo())
                            .build()
            );
        }
    }

    @Override
    public void receive(String subOrderId) {
        //查看订单
        SubOrderPO subOrderPO = findById(subOrderId);
        this.receive(subOrderPO);
    }

    @Override
    public void receive(SubOrderPO subOrderPO) {
        if (subOrderPO.isReceived() && subOrderPO.getStatus() == OrderEnums.Status.ACTIVE) {
            subOrderPO.setStatus(OrderEnums.Status.DONE);
        }
        LocalDateTime convertTime = LocalDateTime.now().plusDays(settleTimeout);
        if (environment.acceptsProfiles(Profiles.of(Constant.PROFILE_DEV, Constant.PROFILE_PRE, Constant.PROFILE_TEST))) {
            convertTime = LocalDateTime.now().plusMinutes(2);
        }
        subOrderPO.setSettlementTime(convertTime);
        save(subOrderPO);

        //设置佣金转正时间
        List<RebateBillPO> rebateBillPOS = rebateBillService.findBySubOrder(subOrderPO.getSubOrderId());
        for (RebateBillPO rebateBillPO : rebateBillPOS) {
            rebateBillPO.setConvertTime(convertTime);
        }
        rebateBillService.saveAll(rebateBillPOS);
        //设置货款转正时间
        List<SettlementBillPO> settlementBillPOS = settlementBillService.findBySubOrder(subOrderPO.getSubOrderId());
        for (SettlementBillPO settlementBillPO : settlementBillPOS) {
            settlementBillPO.setConvertTime(convertTime);
        }
        settlementBillService.saveAll(settlementBillPOS);


    }

    @Override
    public List<SubOrderPO> findByOrder(String dealOrderId) {
        return subOrderRepository.findByOrderPO_OrderId(dealOrderId);
    }

    @Override
    public HSSFWorkbook export(int beginPage, int pageCount, OrderSearchCondition condition) {
        if (pageCount > 10) {
            throw new ApiException(ApiResult.errorWith(ResultCodeEnum.BIZ_ERROR, "导出页数不得超过10页"));
        }

        SpecificationAndSort<SubOrderPO> specification = specificationAndSort(condition);

        Page<SubOrderPO> orderPOS = subOrderRepository.findAll(specification.getSpecification(), ExportPageRequest.of(
                beginPage - 1,
                pageCount,
                Constant.DEFAULT_PAGE_SIZE,
                specification.getSort()
        ));

        String[] th = new String[]{
                "订单号",
                "订单类型",
                "订单状态",
                "商品信息",
                "单价",
                "数量",
                "实付金额",
                "结算货款",
                "收件人",
                "收货人手机",
                "收货地址",
                "发货状态",
                "支付状态",
                "支付时间",
                "创建时间",
                "买家留言"
        };
        boolean isManager = AuthHelper.isManager();
        if (isManager) {
            th = ArrayUtil.addAll(th, new String[]{
                    "所属商家",
                    "一级返佣",
                    "二级返佣",
                    "余额支付"
            });
        }

        List<List<ExcelHelper.CellDesc>> rows = new ArrayList<>();
        orderPOS.forEach(p -> {
            List<ExcelHelper.CellDesc> row = new ArrayList<>();
            row.add(ExcelHelper.asCell(p.getSubOrderId(), Cell.CELL_TYPE_STRING));
            row.add(ExcelHelper.asCell(p.getOrderType().getDesc()));
            row.add(ExcelHelper.asCell(p.getStatus().getDesc()));
            OrderItemPO orderItemPO = p.getOrderItemPOS().get(0);
            row.add(ExcelHelper.asCell(orderItemPO.getGoodsInfo().getGoodsName()));
            row.add(ExcelHelper.asCell(orderItemPO.getGoodsInfo().getPrice(), Cell.CELL_TYPE_NUMERIC));
            row.add(ExcelHelper.asCell(orderItemPO.getNum(), Cell.CELL_TYPE_NUMERIC));
            row.add(ExcelHelper.asCell(p.getFinalAmount(), Cell.CELL_TYPE_NUMERIC));
            row.add(ExcelHelper.asCell(p.payment(), Cell.CELL_TYPE_NUMERIC));
            //因为存在拍卖订单，所以出现收货地址为空的情况，导出时，需要特殊处理一下
            if (p.getOrderPO().getReceivingInfo() != null) {
                row.add(ExcelHelper.asCell(p.getOrderPO().getReceivingInfo().getUserName()));
                row.add(ExcelHelper.asCell(p.getOrderPO().getReceivingInfo().getTelNumber(), Cell.CELL_TYPE_STRING));
                row.add(ExcelHelper.asCell(p.getOrderPO().getReceivingInfo().fullAddress()));
            } else {
                row.add(ExcelHelper.asCell("--"));
                row.add(ExcelHelper.asCell("--"));
                row.add(ExcelHelper.asCell("--"));
            }
            row.add(ExcelHelper.asCell(p.getDeliverStatus().getDesc()));
            row.add(ExcelHelper.asCell(p.getOrderPO().getPayStatus().getDesc()));
            if (p.getOrderPO().getPayTime() == null) {
                row.add(ExcelHelper.asCell(""));
            } else {
                row.add(ExcelHelper.asCell(Jsr310Utils.DateTime.asString(p.getOrderPO().getPayTime())));
            }
            row.add(ExcelHelper.asCell(Jsr310Utils.DateTime.asString(p.getOrderPO().getCreateTime())));
            row.add(ExcelHelper.asCell(p.getMemo()));
            if (isManager) {
                row.add(ExcelHelper.asCell(p.getMerchantPO().getUser().getNickName() + "/" + p.getMerchantPO().getUsername()));
                row.add(ExcelHelper.asCell(p.fatherRebate(), Cell.CELL_TYPE_NUMERIC));
                row.add(ExcelHelper.asCell(p.grantRebate(), Cell.CELL_TYPE_NUMERIC));
                row.add(ExcelHelper.asCell(p.getOrderPO().getBalance(), Cell.CELL_TYPE_NUMERIC));
            }
            rows.add(row);
        });

        return ExcelHelper.createWorkbook(ExcelHelper.Sheet.builder()
                .name("订单列表")
                .th(th)
                .rows(rows)
                .build());
    }

    @Override
    @Transactional
    public void convert(String subOrderId) {
        //佣金转正
        List<RebateBillPO> pos = rebateBillService.findBySubOrder(subOrderId);
        for (RebateBillPO po : pos) {
            rebateBillService.convert(po);
        }
        //货款结算
        List<SettlementBillPO> billPOS = settlementBillService.findBySubOrder(subOrderId);
        for (SettlementBillPO billPO : billPOS) {
            settlementBillService.convert(billPO);
        }
        //设置订单的结算状态为已结算
        subOrderRepository.amountDone(subOrderId);
    }
}
